Skip to content

Expose through extension methods maui build and launch arguments - #19572

Open
Frederik St-Onge (frederikstonge) wants to merge 6 commits into
microsoft:mainfrom
frederikstonge:expose-maui-build-launch-args
Open

Expose through extension methods maui build and launch arguments#19572
Frederik St-Onge (frederikstonge) wants to merge 6 commits into
microsoft:mainfrom
frederikstonge:expose-maui-build-launch-args

Conversation

@frederikstonge

Copy link
Copy Markdown

Description

Allow users to edit maui build and launch arguments to give more flexibility.

Fixes #19571

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No

Copilot AI balanced review requested due to automatic review settings August 21, 2026 14:57
@frederikstonge

Copy link
Copy Markdown
Author

Frederik St-Onge (Frederik St-Onge (@frederikstonge)) please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@microsoft-github-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
@microsoft-github-policy-service agree
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
@microsoft-github-policy-service agree company="Microsoft"

Contributor License Agreement

@microsoft-github-policy-service agree

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 19572

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 19572"

@github-actions github-actions Bot added the area-integrations Issues pertaining to Aspire Integrations packages label Aug 21, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds public MAUI APIs for customizing build and launch command arguments within the startup pipeline.

Changes:

  • Adds synchronous and asynchronous argument callbacks.
  • Applies callbacks during MAUI build/launch orchestration.
  • Adds documentation and unit tests.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/Aspire.Hosting.Maui/Annotations/MauiBuildArgumentsCallbackAnnotation.cs Stores argument callbacks and target steps.
src/Aspire.Hosting.Maui/Lifecycle/MauiBuildQueueEventSubscriber.cs Invokes callbacks during startup.
src/Aspire.Hosting.Maui/MauiBuildArgumentsCallbackContext.cs Exposes callback state and mutable arguments.
src/Aspire.Hosting.Maui/MauiBuildArgumentsExtensions.cs Adds public fluent configuration APIs.
src/Aspire.Hosting.Maui/MauiBuildStep.cs Defines build and launch phases.
src/Aspire.Hosting.Maui/README.md Documents argument customization.
tests/Aspire.Hosting.Maui.Tests/MauiBuildArgumentsExtensionsTests.cs Tests API registration and callback behavior.

Comment thread src/Aspire.Hosting.Maui/Lifecycle/MauiBuildQueueEventSubscriber.cs
Comment thread src/Aspire.Hosting.Maui/Lifecycle/MauiBuildQueueEventSubscriber.cs Outdated
Comment thread tests/Aspire.Hosting.Maui.Tests/MauiBuildArgumentsExtensionsTests.cs Outdated
Comment thread src/Aspire.Hosting.Maui/README.md
Comment thread src/Aspire.Hosting.Maui/MauiBuildArgumentsExtensions.cs
Copilot AI review requested due to automatic review settings August 21, 2026 15:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Suppressed comments (6)

Previously missed (1) — in code that hasn't changed since the last review.

src/Aspire.Hosting.Maui/MauiBuildArgumentsExtensions.cs:112

  • This example recommends overriding NoBuild=true, but that flag is required by the serialized build queue: the semaphore is released when DCP reports the launch process Running, before any launch-time build finishes, allowing the next target to build concurrently. Keep NoBuild=true and use a non-build-triggering launch property in the example, unless the queue lifecycle is redesigned.

This issue also appears on line 143 of the same file.

    /// <example>
    /// Force a build during launch by overriding the default <c>-p:NoBuild=true</c>:
    /// <code lang="csharp">
    /// maui.AddAndroidEmulator("emulator")
    ///     .WithMauiLaunchArguments(context => context.Arguments.Add("-p:NoBuild=false"));
    /// </code>

src/Aspire.Hosting.Maui/Lifecycle/MauiBuildQueueEventSubscriber.cs:293

  • This is not idempotent as the new regression test expects. After the first BeforeStartEvent, launchOverride is the replacement annotation containing the callback's additions, so a second event copies those edited arguments and applies the callbacks again (and the test helper also registers another subscriber). The WithMauiLaunchArguments_MultipleStarts_DoNotAccumulate assertion will therefore observe duplicate -p:NoBuild=false entries. Preserve the pristine override separately or mark the launch callbacks as applied so repeated startup processing cannot reapply them.
        var arguments = new List<string>(launchOverride.Arguments);

src/Aspire.Hosting.Maui/README.md:244

  • This TypeScript example does not match the generated SDK. A getter-only IList<string> is exposed as the async context.arguments() method returning an AspireList<string>, whose mutation API is add; context.arguments.push(...) therefore does not compile. Fetch the list and await add in an async callback.
    .withMauiBuildArguments(context => { context.arguments.push("-p:MyProperty=Value"); });

src/Aspire.Hosting.Maui/MauiBuildArgumentsExtensions.cs:42

  • These are new exported callback APIs, but the PR only adds C# tests; the existing MAUI polyglot AppHost does not exercise either generated method or its callback context. Add a TypeScript polyglot scenario that mutates the generated arguments editor. This is especially important here because the README currently uses a generated API shape that does not compile.
    [AspireExport]
    public static IResourceBuilder<T> WithMauiBuildArguments<T>(
        this IResourceBuilder<T> builder,
        Func<MauiBuildArgumentsCallbackContext, Task> callback)

src/Aspire.Hosting.Maui/README.md:258

  • This TypeScript example does not match the generated SDK. A getter-only IList<string> is exposed as the async context.arguments() method returning an AspireList<string>, whose mutation API is add; context.arguments.push(...) therefore does not compile. Fetch the list and await add in an async callback.
    .withMauiLaunchArguments(context => { context.arguments.push("-p:NoBuild=false"); });

src/Aspire.Hosting.Maui/MauiBuildArgumentsExtensions.cs:148

  • This example also recommends overriding NoBuild=true, which breaks the build queue's serialization because the semaphore can be released as soon as the launch process reports Running, before launch-time compilation completes. Keep NoBuild=true and demonstrate a property that does not trigger another build.
    /// <example>
    /// Force a build during launch by overriding the default <c>-p:NoBuild=true</c>:
    /// <code lang="csharp">
    /// maui.AddAndroidEmulator("emulator")
    ///     .WithMauiLaunchArguments(context => context.Arguments.Add("-p:NoBuild=false"));
    /// </code>

Comment thread src/Aspire.Hosting.Maui/Lifecycle/MauiBuildQueueEventSubscriber.cs Outdated
Comment thread src/Aspire.Hosting.Maui/README.md Outdated
Copilot AI review requested due to automatic review settings August 21, 2026 16:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Suppressed comments (4)

Previously missed (2) — in code that hasn't changed since the last review.

src/Aspire.Hosting.Maui/Lifecycle/MauiBuildQueueEventSubscriber.cs:293

  • Repeated application is not idempotent: this copies the last override, which already contains the prior callback result. An append-style launch callback therefore adds its argument again whenever BeforeStartEvent is published again, contrary to the documented contract and the new multiple-start test. Preserve the pristine launch override separately and rebuild the callback result from that baseline each time.
        var arguments = new List<string>(launchOverride.Arguments);

src/Aspire.Hosting.Maui/MauiBuildArgumentsCallbackContext.cs:51

  • This token is not always tied to a resource start. Launch callbacks receive the BeforeStartEvent token, while build callbacks receive the linked per-resource token, so the current public documentation promises cancellation semantics that launch callbacks do not have.
    /// Gets a token that is cancelled if the resource start is cancelled.

src/Aspire.Hosting.Maui/README.md:258

  • This TypeScript sample uses an API shape the generated SDK does not provide. arguments is an async getter returning an AspireList<string>, whose mutation method is asynchronous add; it is not an array with push.
    .withMauiLaunchArguments(context => { context.arguments.push("-p:MyProperty=Value"); });

src/Aspire.Hosting.Maui/MauiBuildArgumentsExtensions.cs:39

  • The new exported callback contract has only C# tests that invoke annotations directly. Because the callback context projects a live mutable list into generated SDKs, add ATS/TypeScript generation coverage for both methods and the arguments(): Promise<AspireList<string>> mutation path; this would also catch the invalid TypeScript examples in this PR.
    [AspireExport]

await PublishBeforeStartAsync(app);

var launchOverride = Assert.Single(emulator.Resource.Annotations.OfType<ProjectLaunchArgsOverrideAnnotation>());
Assert.Equal(["build", "--no-restore", "/t:Run", "-p:NoBuild=true", "-p:NoBuild=false"], launchOverride.Arguments);
Comment thread src/Aspire.Hosting.Maui/README.md Outdated
Comment on lines +243 to +244
mauiApp.addAndroidEmulator("emulator")
.withMauiBuildArguments(context => { context.arguments.push("-p:MyProperty=Value"); });
Copilot AI review requested due to automatic review settings August 21, 2026 16:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

src/Aspire.Hosting.Maui/MauiBuildArgumentsCallbackContext.cs:51

  • This description is inaccurate for launch callbacks: they run from BeforeStartEvent and receive the application-start cancellation token, before any individual resource start can be canceled. Describe this as cancellation of the current callback operation so the documentation is correct for both build and launch steps.
    /// <summary>
    /// Gets a token that is cancelled if the resource start is cancelled.
    /// </summary>

src/Aspire.Hosting.Maui/README.md:264

  • This TypeScript fluent call is also left unawaited, so the sample relies on implicit promise flushing and defers failures. Await the registration chain directly.
mauiApp.addAndroidEmulator("emulator")
    .withMauiLaunchArguments(async context => {
        const args = await context.arguments();
        await args.add("-p:MyProperty=Value");
    });

Comment thread src/Aspire.Hosting.Maui/README.md Outdated
Comment thread src/Aspire.Hosting.Maui/MauiBuildArgumentsCallbackContext.cs Outdated
/// Mutate <see cref="Arguments"/> in place to add, remove, or replace the arguments that will be
/// passed to <c>dotnet</c> for the <see cref="Step"/> this callback is registered for.
/// </remarks>
[AspireExport(ExposeProperties = true)]
Copilot AI review requested due to automatic review settings August 21, 2026 17:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

// redact them before logging. Ordinal comparison because these are literal command-line tokens.
private readonly HashSet<string> _sensitiveArguments = new(StringComparer.Ordinal);

private readonly IList<string> _arguments;
/// build pipeline replaces its value with a placeholder in the arguments it logs to the resource
/// output. Launch-step arguments are additionally masked by the dashboard's command-line display.
/// </remarks>
public void AddArgument(string argument, bool isSensitive = false)
Comment on lines +57 to +61
/// <summary>
/// Appends an argument whose value is sensitive (for example <c>-p:AndroidSigningKeyPass=…</c>).
/// </summary>
/// <param name="argument">The full argument to add.</param>
/// <param name="isSensitive"></param>
Comment on lines +269 to +273
MSBuild properties can carry secrets (for example a signing key password). Add those with `AddSensitiveArgument` instead of `Arguments.Add`. The value is still passed to `dotnet` verbatim, but the build pipeline redacts it from the arguments it writes to the resource logs. Launch-step arguments are additionally masked by the dashboard's command-line display.

```csharp
mauiApp.AddAndroidEmulator()
.WithMauiBuildArguments(context => context.AddSensitiveArgument($"-p:AndroidSigningKeyPass={keyPassword}"));
Comment on lines +72 to +75
if (isSensitive)
{
_sensitiveArguments.Add(argument);
}

@adamint Adam Ratzman (adamint) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking this on. I reproduced #19571 against the base branch and reviewed the current head (0bf8a933) end to end.

I don't think this is ready yet:

  • A clean rebuild of Aspire.Hosting.Maui fails with four CS1574 errors because the public docs reference a missing MauiBuildArgumentsCallbackContext.Arguments member.
  • The generated TypeScript surface cannot support the checked-in context.arguments() / context.addArgument(...) fixture as implemented.
  • isSensitive only redacts the MAUI build log. Launch arguments are copied into a string-only ProjectLaunchArgsOverrideAnnotation, so the README's dashboard-masking guarantee does not hold.
  • With only warnings-as-errors relaxed, the focused test class runs 18 tests and 2 fail before reaching the launch callback because the builder has no DCP paths.

I left two additional inline comments for the failing lifecycle coverage and launch-argument precedence.

var tempFile = Path.Combine(workspace.Path, "TempMauiProject.csproj");
File.WriteAllText(tempFile, MauiTestHelper.CreateProjectContent("net10.0-android"));

var appBuilder = DistributedApplication.CreateBuilder();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two lifecycle tests are currently red. With only TreatWarningsAsErrors disabled to get past the XML-doc errors, the focused class runs 18 tests and these two fail in DcpNameGenerator because DistributedApplication.CreateBuilder() leaves CliPath and DashboardPath unset. Please use TestDistributedApplicationBuilder.Create() (as the nearby MauiPlatformExtensionsTests does), or otherwise configure the required paths, so the assertions actually exercise OnBeforeStartAsync.

// Swap the immutable annotation for one carrying the updated arguments; DCP reads the last
// override annotation when it renders the launch command during PrepareProjectExecutables.
resource.Annotations.Remove(launchOverride);
resource.Annotations.Add(new ProjectLaunchArgsOverrideAnnotation(arguments, launchOverride.LeadingResourceArgumentToRemove));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This callback edits only ProjectLaunchArgsOverrideAnnotation, not the complete launch argument list. DCP emits that override before resource arguments, while ConfigurePlatformResource adds the MAUI TFM/device properties through WithArgs. A callback that adds -p:AdbTarget=..., -p:_DeviceName=..., or -p:RuntimeIdentifier=... is therefore followed by MAUI's original value and loses under MSBuild's last-value-wins behavior. Please apply the callback to the complete evaluated launch arguments, or otherwise ensure callback edits are emitted last; as written, callers cannot reliably override existing MAUI launch properties.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-integrations Issues pertaining to Aspire Integrations packages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Aspire.Hosting.Maui] Expose build and launch arguments for more flexibility

3 participants